package org.javanum.number;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;

import static org.javanum.number.TestUtils.*;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;

/**
 * User: Scott Fines
 * Date: Nov 26, 2009
 * Time: 9:40:47 AM
 */
@RunWith(Parameterized.class)
public class PolarComplexTest {
    private final PolarComplex firstNum;
    private final PolarComplex secondNum;
    private final Real real;

    public PolarComplexTest(PolarComplex firstNum,
                            PolarComplex secondNum, Real real) {
        this.firstNum = firstNum;
        this.secondNum = secondNum;
        this.real = real;
    }

    @Parameterized.Parameters
    public static Collection<Object[]> getParameters(){
        Random rand = new Random();
		int randomDataValue = 10;
		List<Object[]> data =
                new ArrayList<Object[]>(randomDataValue);
		for(int i=randomDataValue;i<=randomDataValue;i++){
            for(int j=-randomDataValue;j<=randomDataValue;j++){
                data.add(new Object[]{
                        PolarComplex.valueOf(i,j),
                        PolarComplex.valueOf(2*i,2*j),
                        Real.valueOf(j)
                });
            }
		}
		return data;
    }

/*-------------------------------------------------------*/
    /*Equality tests*/

    @SuppressWarnings({"ObjectEqualsNull"})
    @Test
    public void testEqualsNullityContract() throws Exception {
        PolarComplex z = getOneNumber();
        assertNotNull(z);


        PolarComplex x = getOneNumber();
        assumeNotNull(x);
        assertFalse(
                String.format("Nullity fails: z.equals(null)=true; z=%1$s",z),
                z.equals(null));
    }

    @Test
    public void testEqualsReflexivityContract() throws Exception {
        /*
        Tests that z = z for any PolarComplex z
         */
        PolarComplex z = getOneNumber();
        assertTrue(String.format("Reflexivity fails: z!=z;z=%1$s",z),
                            z.equals(z));
    }

    @Test
    public void testEqualsSymmetryContract() throws Exception {
        /*
        Tests that z=y if y=z for any PolarComplex z
         */
        PolarComplex z = getOneNumber();
        assumeNotNull(z);
        PolarComplex y = PolarComplex.valueOf(z.magnitude,z.angle);
        assertTrue(String.format("Reflexivity fails: z!=y, but y is " +
                "constructed from z%n" +
                "z=%1$s%n" +
                "y=%2$s",z,y),z.equals(y));

        assertTrue(String.format("Reflexivity fails:y!=z, but y is" +
                "constructed from z%n" +
                "z=%1$s%n" +
                "y=%2$s",z,y),y.equals(z));
    }

    @Test
    public void testTransitivityContract() throws Exception {
        PolarComplex z = getOneNumber();
        assumeNotNull(z);
        PolarComplex y = PolarComplex.valueOf(z.magnitude,z.angle);
        PolarComplex x = PolarComplex.valueOf(y.magnitude,y.angle);

        assertTrue(String.format("Transitivity fails: z!=y, but y " +
                "should be the same as z%n" +
                "z=%1$s%n" +
                "y=%2$s",z,y),z.equals(y));
        assertTrue(String.format("Transitivity fails: y!=x but x " +
                "should be the same as y%n" +
                "y=%1$s%n" +
                "x=%2$s",y,x),y.equals(x));

        assertTrue(String.format("Transitivity fails:z!=x, but" +
                "z=y and y=x%n" +
                "z=%1$s%n" +
                "x=%2$s",z,x),z.equals(x));
    }

    @Test
    public void testNonEquals() throws Exception {
        PolarComplex x = firstNum;
        PolarComplex y = secondNum;
        assumeNotNull(x);
        assumeNotNull(y);
        assumeTrue(
                x.magnitude()!=y.magnitude()&&x.angle()!=y.angle());

        assertFalse("Non equal elements evaluate equals() to true; " +
                "x="+x+" ,y="+y,x.equals(y));
    }

/*-------------------------------------------------------*/
    /*Hashcode tests*/

    @Test
    public void testHashCodeEqualElementsEqual() throws Exception {
        /*
        Tests that the hashcode of equal elements are equal
         */
        PolarComplex z = getOneNumber();
        assumeNotNull(z);
        assumeNotNaN(z);
        assumeNotComplexInfinity(z);

        PolarComplex w = PolarComplex.valueOf(z.magnitude,z.angle);
        assumeTrue(z.equals(w));

        assertEquals(String.format("Equal elements have " +
                "different hashes%n" +
                "z=%1$s%n" +
                "w=%2$s",z,w),z.hashCode(),w.hashCode());

    }

    @Test
    public void testHashCodeNonEqualElementsUnequal() throws Exception {
        PolarComplex x = firstNum;
        PolarComplex z = secondNum;
        assumeNotNull(x,z);
        assumeNotNaN(x,z);
        assumeNotComplexInfinity(x,z);
        assumeTrue(!z.equals(x));

        assertTrue("Unequal elements hash to the same value;" +
                "x="+x+" ,z="+z,x.hashCode()!=z.hashCode());
    }

/*-------------------------------------------------------*/
    /*Conjugate, Modulus, and Negation tests*/

    @Test
    public void testConjugate() throws Exception {
        PolarComplex x = getOneNumber();
        assumeNotNull(x);
        assumeNotNaN(x);
        assumeNotComplexInfinity(x);

        PolarComplex conjugate = x.conjugate();

        PolarComplex correctConj = PolarComplex.valueOf(x.magnitude,-x.angle);

        assertComplexEquality("Conjugate incorrect",
                            correctConj,conjugate,DELTA);
    }

    @Test
    public void testModulus() throws Exception {
        PolarComplex x = getOneNumber();
        assumeNotNull(x);
        assumeNotComplexInfinity(x);
        assumeNotNaN(x);

        double modulus = x.modulus();
        double realMod = x.magnitude;

        assertEquals("Modulus not correct: " +
                "modulus="+modulus+", realMod="+realMod,
                modulus,realMod,DELTA);
    }

    @Test
    public void testNegation() throws Exception {
        PolarComplex z = getOneNumber();
        assumeNotNull(z);
        assumeNotNaN(z);
        assumeNotComplexInfinity(z);

        PolarComplex neg = z.negate();

        PolarComplex correct = PolarComplex.valueOf(z.magnitude,z.angle-Math.PI);
        assertComplexEquality("negation fails",correct, neg,DELTA);
    }

/*-------------------------------------------------------*/
    /*Addition tests*/

    @Test
    public void testAddComplex() throws Exception {
        PolarComplex x = firstNum;
        PolarComplex y = secondNum;
        assumeNotNull(x,y);
        assumeNotComplexInfinity(x,y);
        assumeNotNaN(x,y);

        PolarComplex z = x.add(y);
        PolarComplex diff = z.subtract(y);

        assertComplexEquality("x+y-y!=x",x,diff,DELTA);
    }

    @Test
    public void testAddReal() throws Exception {
        PolarComplex z = getOneNumber();
        assumeNotNull(z,real);
        assumeNotNaN(z);
        assumeNotNaN(real);
        assumeNotComplexInfinity(z);
        assumeNotRealInfinity(real);

        PolarComplex sum = z.add(real);
        PolarComplex asComplex = real.asPolarComplex();
        PolarComplex correctSum = asComplex.add(z);

        assertComplexEquality("z+real incorrect",correctSum,sum,DELTA);
    }

    @Test
    public void testAdditiveInverse() throws Exception {
        PolarComplex x = getOneNumber();
        assumeNotNull(x);
        assumeNotNaN(x);
        assumeNotComplexInfinity(x);
        assumeNotZero(x);

        //TODO -sf- deal with underflow!
        PolarComplex y = x.additiveInverse();
        System.out.println(String.format("x=%1$s",x));
        System.out.println(String.format("y=%1$s",y));

        PolarComplex z = x.add(y);
        assertEquals("x.add(additiveInverse(x))!=0",0d,z.magnitude,DELTA);
        /*
        Note: Angle does not matter if magnitude is zero, we are good
         */
    }


/*-------------------------------------------------------*/
    /*Subtraction tests*/

    @Test
    public void testSubtractComplex() throws Exception {
        PolarComplex x = firstNum;
        PolarComplex y = secondNum;
        assumeNotNull(x,y);
        assumeNotComplexInfinity(x,y);
        assumeNotNaN(x,y);

        PolarComplex z = x.subtract(y);
        PolarComplex shouldBeX = z.add(y);

        assertComplexEquality("x-y+y!=x",x,shouldBeX,DELTA); 
    }

    @Test
    public void testSubtractReal() throws Exception {
        PolarComplex z = getOneNumber();
        assumeNotNull(z,real);
        assumeNotNaN(z);
        assumeNotNaN(real);
        assumeNotComplexInfinity(z);
        assumeNotRealInfinity(real);

        PolarComplex diff = z.subtract(real);
        PolarComplex asComplex = real.asPolarComplex();
        PolarComplex correctDiff = z.subtract(asComplex);

        assertComplexEquality("z-real incorrect",correctDiff,diff,DELTA);
    }

    /*-------------------------------------------------------*/
    /*Multiplication tests*/

    @Test
    public void testMultiplyComplex() throws Exception {
        PolarComplex x = firstNum;
        PolarComplex y = secondNum;
        assumeNotNull(x,y);
        assumeNotNaN(x,y);
        assumeNotComplexInfinity(x,y);

        PolarComplex z = x.multiply(y);

        double expectedMag =
                x.magnitude*y.magnitude;
        double expectedAngle =
                x.angle+y.angle;

        PolarComplex expected = PolarComplex.valueOf(expectedMag,expectedAngle);

        assertComplexEquality("x*y incorrect",expected,z,DELTA);
    }

    @Test
    public void testMultiplyReal() throws Exception {
        PolarComplex z = getOneNumber();
        assumeNotNull(z,real);
        assumeNotNaN(z);
        assumeNotNaN(real);
        assumeNotComplexInfinity(z);
        assumeNotRealInfinity(real);

        System.out.println(String.format("   z=%1$s",z));
        System.out.println(String.format("real=%1$s",real));
        
        PolarComplex mult = z.multiply(real);
        PolarComplex asComplex = real.asPolarComplex();
        PolarComplex correctMult = z.multiply(asComplex);

        assertComplexEquality("z*real incorrect",correctMult,mult,DELTA);
    }

    @Test
    public void testMultiplicativeInverse() throws Exception {
        PolarComplex x = getOneNumber();
        assumeNotNull(x);
        assumeTrue(x.magnitude!=0);

        PolarComplex y = x.multiplicativeInverse();

        PolarComplex z = x.multiply(y);

        assertComplexEquality("x.multiply(1/x)!=1",
                                PolarComplex.ONE,z,DELTA);
    }

/*-------------------------------------------------------*/
    /*Division tests*/
    @Test
    public void testDivideComplex() throws Exception {
        PolarComplex x = firstNum;
        PolarComplex y = secondNum;
        assumeNotNull(x,y);
        assumeNotZero(y);
        assumeNotComplexInfinity(x,y);
        assumeNotNaN(x,y);

        PolarComplex z = x.divide(y);

        double resultMag =
                x.magnitude/y.magnitude;
        double resultAngle =
                x.angle-y.angle;

        PolarComplex correct = PolarComplex.valueOf(resultMag,resultAngle);

        assertComplexEquality("z/y incorrect",correct,z,DELTA);
    }

    @Test
    public void testDivideReal() throws Exception {
        PolarComplex z = getOneNumber();
        assumeNotNull(z,real);
        assumeNotNaN(z);
        assumeNotNaN(real);
        assumeNotComplexInfinity(z);
        assumeNotRealInfinity(real);
        assumeNotZero(real);

        PolarComplex div = z.divide(real);
        PolarComplex asComplex = real.asPolarComplex();
        PolarComplex correctDiv = z.divide(asComplex);

        assertComplexEquality("z*real incorrect",correctDiv,div,DELTA);
    }

/*-------------------------------------------------------*/
    /*Conversion tests*/

    @Test
    public void testConvertToCartesianForm() throws Exception {
        PolarComplex z = getOneNumber();
        assumeNotNull(z);
        assumeNotNaN(z);
        assumeNotComplexInfinity(z);

        Complex cartesianForm = z.cartesianForm();

        double real = z.magnitude*Math.cos(z.angle);
        double imag = z.magnitude*Math.sin(z.angle);

        Complex correct = Complex.valueOf(real,imag);

        assertComplexEquality("cartesianForm() fails",
                            correct,cartesianForm,DELTA);
    }

    @Test
    public void testCartesianFormPolarForm() throws Exception {
        /*
        tests that z.cartesianForm().polarForm()=z for any
        PolarComplex z
         */
        PolarComplex z = getOneNumber();
        assumeNotNull(z);
        assumeNotNaN(z);
        assumeNotComplexInfinity(z);
        assumeNotZero(z);

        Complex cart = z.cartesianForm();
        PolarComplex polar = cart.polarForm();

        System.out.println(String.format(" cart=%1$s",cart));
        System.out.println(String.format("polar=%1$s",polar));

        assertComplexEquality("z.cartesianForm().polarForm()!=z",
                z,polar,DELTA);
    }

    /*-------------------------------------------------------*/
    /*Helper Methods */

    private PolarComplex getOneNumber() {
        if(firstNum==null)
            return secondNum;
        return firstNum;
    }

}
